#!/bin/bash
# Copyright (C) 2024-2025 KoudaiPC Computer Technologies Co., Ltd.
# script name: yandi-prepare-sysupgrade.sh
# author: david921518@qq.com

################
# global vars
GLB_WORK_DIR=
GLB_DONE_FILE=
GLB_SYSUPGRADE_ROOT=
GLB_SYSUPGRADE_BAK=
################

user=$(whoami)

# 定义日志格式-前缀
log_prefix="[prepare-sysuprade]"
# 定义日志目录和文件名
log_file_dir="/var/log/kpc"
log_file_path="$log_file_dir/sysupgrade.log"

log_kmsg_path="/dev/kmsg"
log_con_path="/dev/tty1"

function log_init()
{
  # 文件不存在则创建
  if [ ! -d "$log_file_dir" ]; then
    mkdir -p $log_file_dir
    chmod -R 755 $log_file_dir
  fi
  if [ ! -f "$log_file_path" ]; then
    touch $log_file_path
  fi
}

# 定义日志打印格式
function log()
{
  # 定义日志时间格式
  local curtime=$(date "+%Y-%m-%d %H:%M:%S")
  echo "$curtime $log_prefix $@"
  if [ "$user" == "root" ]; then
    echo "$curtime $log_prefix $@" >>$log_file_path
    #sleep 0.1
    echo "$log_prefix [kmsg] $@" >$log_kmsg_path
    sleep 0.1
    #echo "$curtime $log_prefix [con] $@" >$log_con_path
    #sleep 0.1
  fi
}

function log_from_file()
{
  tmpfile=$@
  if [ ! -f "$tmpfile" ]; then
    log "no file [$tmpfile] to log"
    return 1
  fi

  local n=0
  local -a lists
  while read line;
  do
    lists[$n]="$line"
    ((n++))
  done <$tmpfile

  # 定义日志时间格式
  local curtime=$(date "+%Y-%m-%d %H:%M:%S")

  #echo "$curtime $log_prefix $@"
  local i=0
  for i in $( seq 0 $((${#lists[*]} - 1)) )
  do
    echo "$curtime $log_prefix ${lists[i]}"
  done

  if [ "$user" == "root" ]; then
    #echo "$curtime $log_prefix $@" >>$log_file_path
    for i in $( seq 0 $((${#lists[*]} - 1)) )
    do
      echo "$curtime $log_prefix ${lists[i]}" >>$log_file_path
    done

    #sleep 0.1

    #echo "$log_prefix [kmsg] $@" >$log_kmsg_path
    for i in $( seq 0 $((${#lists[*]} - 1)) )
    do
      echo "$curtime $log_prefix ${lists[i]}" >$log_kmsg_path
      [ $((i%10)) -ne 9 ] || sleep 0.1
    done

    #sleep 0.1

    #echo "$curtime $log_prefix [con] $@" >$log_con_path
    #for i in $( seq 0 $((${#lists[*]} - 1)) )
    #do
    #  echo "$curtime $log_prefix ${lists[i]}" >$log_con_path
    #done

    #sleep 0.1
  fi
}

# loop forever to reboot
function force_reboot()
{
  log "force reboot now!"
  while true; do reboot; sleep 9; done
}

# clean up firstboot script
function cleanup_firstboot_script()
{
  local firstboot_file=/etc/zzz-firstboot
  if [ -f $firstboot_file ]; then
    log "rm -f $firstboot_file"
    rm -f $firstboot_file
  fi
}

# fix all kinds of possible broken issues
function fix_possible_broken()
{
  log "apt-get --fix-broken install -y"
  apt-get --fix-broken install -y

  log "dpkg -C"
  dpkg -C
  log "dpkg --configure -a"
  dpkg --configure -a
}

function get_work_dir()
{
  ################
  # /kpcos/distro/yandi/releases/0.5/ports/arm64/guest/yandi-prepare-sysupgrade.sh
  # /kpcos/distro/yandi/releases/0.5/ports/arm64/root/etc/zzz-sysupgrade
  ################

  local real_path=$(realpath $0)
  GLB_WORK_DIR=$(dirname "$real_path")
  log "WORK DIR is [$GLB_WORK_DIR]"

  local done_file="${real_path}_done"
  log "done_file=[$done_file]"
  real_done_file=$(realpath "$done_file")
  log "real_done_file=[$real_done_file]"

  GLB_DONE_FILE="$real_done_file"
  log "GLB_DONE_FILE=[$GLB_DONE_FILE]"

  local sysupgrade_root="$GLB_WORK_DIR/../root"
  log "sysupgrade_root=[$sysupgrade_root]"
  real_sysupgrade_root=$(realpath "$sysupgrade_root")
  log "real_sysupgrade_root=[$real_sysupgrade_root]"

  if [ ! -d $real_sysupgrade_root ]; then
    log "No sysupgrade root dir [$real_sysupgrade_root]."
    exit 1
  fi

  GLB_SYSUPGRADE_ROOT="$real_sysupgrade_root"
  log "GLB_SYSUPGRADE_ROOT=[$GLB_SYSUPGRADE_ROOT]"

  local sysupgrade_bak="$GLB_WORK_DIR/../backup"
  log "sysupgrade_bak=[$sysupgrade_bak]"
  real_sysupgrade_bak=$(realpath "$sysupgrade_bak")
  log "real_sysupgrade_bak=[$real_sysupgrade_bak]"

  log "mkdir -p $real_sysupgrade_bak"
  mkdir -p $real_sysupgrade_bak

  if [ ! -d $real_sysupgrade_bak ]; then
    log "No sysupgrade backup dir [$real_sysupgrade_bak]."
    exit 1
  fi

  GLB_SYSUPGRADE_BAK="$real_sysupgrade_bak"
  log "GLB_SYSUPGRADE_BAK=[$GLB_SYSUPGRADE_BAK]"
}

# save installed packages list
function save_pkg_list()
{
  if [ "$1" == "" ]; then
    log "save_pkg_list() filename is empty, do nothing!"
    return
  fi
  local list_file="$PWD"/$1
  log "save bootstrap packages list to $list_file"
  [ -f $list_file ] || dpkg -l >$list_file
}

# get ROOT device from mount info
function get_root_dev()
{
  local line=$(mount | grep " on / type ")
  if [ x"$line" != x ]; then
    root_part=$(echo $line | awk '{print $1}')
    log "root_part=[$root_part]"
    case "$root_part" in
      "/dev/sd"* )
        GLB_ROOT_DEV=${root_part:0:8}
        GLB_ROOT_PART=$root_part
        ;;
      "/dev/vd"* )
        GLB_ROOT_DEV=${root_part:0:8}
        GLB_ROOT_PART=$root_part
        ;;
      * )
        ;;
    esac
    log "GLB_ROOT_DEV=[$GLB_ROOT_DEV] GLB_ROOT_PART=[$GLB_ROOT_PART]"
  fi
}

# start kpc-heartbeat
function start_heartbeat()
{
  heartbeat_conf=/etc/kpc/heartbeat.conf
  heartbeat_file=/usr/sbin/kpc-heartbeat.sh
  heartbeat_lock=/tmp/kpc-hearbeat.lock
  if [ -x $heartbeat_file ]; then
    log "start kpc-heartbeat"
    (flock -n $heartbeat_lock -c "echo 9 >$heartbeat_conf ; ($heartbeat_file)")&
  fi
}

# run bootstrap configurating
function run_bootstrap_config()
{
  local wifi_backend=""
  local wifi_dev=""
  local wifi_addr=""
  local wifi_mode=""
  local wifi_net=""
  local wifi_pass=""

  local conf_file=/etc/kpc/bootstrap.conf
  if [ -f $conf_file ]; then
    log "start to configurate bootstrap environment"

    local n=0
    local -a lists
    while read line;
    do
      lists[$n]="$line"
      ((n++))
    done <$conf_file

    local i=0
    for i in $( seq 0 $((${#lists[*]} - 1)) )
    do
      #log "lists[$i]=[${lists[i]}]"
      local name=$(echo ${lists[i]} | awk '{print $1}')
      local val=$(echo ${lists[i]} | awk '{print $2}')
      #log "name=[$name] val=[$val]"
      case "$name" in
        "#"* )
          #log "it's comment"
          ;;
        "" )
          #log "it's empty line"
          ;;
        "wifi.backend" )
          if [ "$val" == "iwd" ]; then
            log "enable iwd network configuration"
            wifi_backend="$val"
            iwd_main_conf=/etc/iwd/main.conf
            sed "s/#EnableNetworkConfiguration=true/EnableNetworkConfiguration=true/g" -i $iwd_main_conf
            #log_from_file $iwd_main_conf
          fi
          ;;
        "wifi.device_name" )
          wifi_dev="$val"
          log "wifi_dev=[$wifi_dev]"
          ;;
        "wifi.device_address" )
          wifi_addr="$val"
          log "wifi_addr=[$wifi_addr]"
          ;;
        "wifi.mode" )
          wifi_mode="$val"
          log "wifi_mode=[$wifi_mode]"
          ;;
        "wifi.network" )
          wifi_net="$val"
          log "wifi_net=[$wifi_net]"
          ;;
        "wifi.passphrase" )
          wifi_pass="$val"
          log "wifi_pass=[$wifi_pass]"
          ;;
        * )
          log "unknown option=[$name]"
          ;;
      esac
    done

    if [ "$wifi_backend" == "iwd" ]; then
      log "systemctl --now enable iwd"
      systemctl --now enable iwd
      sleep 3
      if [ x"$wifi_dev" == x ]; then
        #iwctl device list
        line=$(iwctl device list | grep "station" )
        log "line=[$line]"
        if [ x"$line" == x ]; then
          log "no station mode wifi device found, exit now"
          exit 1
        fi
        #dummy=$(echo $line | awk '{print $1}')
        dummy=$(echo $line | awk '{print $1}')
        log "dummy=[$dummy]"
        #dev_name=$(echo $line | awk '{print $2}')
        wifi_dev=$(echo $line | awk '{print $2}')
        log "wifi_dev=[$wifi_dev]"
        #dev_addr=$(echo $line | awk '{print $3}')
        wifi_addr=$(echo $line | awk '{print $3}')
        log "wifi_addr=[$wifi_addr]"
        #dev_powered=$(echo $line | awk '{print $4}')
        wifi_powered=$(echo $line | awk '{print $4}')
        log "wifi_powered=[$wifi_powered]"
        #dev_adapter=$(echo $line | awk '{print $5}')
        wifi_adapter=$(echo $line | awk '{print $5}')
        log "wifi_adapter=[$wifi_adapter]"
        #dev_mode=$(echo $line | awk '{print $6}')
        wifi_mode=$(echo $line | awk '{print $6}')
        log "wifi_mode=[$wifi_mode]"

        log "detect wifi_dev=[$wifi_dev]"
        if [ x"$wifi_dev" == x ]; then
          log "no wifi device found, exit now"
          exit 1
        fi
      fi
      if [ x"$wifi_dev" != x ]; then
        line=$(iwctl device list | grep " $wifi_dev " )
        log "line=[$line]"
        if [ x"$line" != x ]; then
          dummy=$(echo $line | awk '{print $1}')
          dev_name=$(echo $line | awk '{print $2}')
          dev_addr=$(echo $line | awk '{print $3}')
          dev_powered=$(echo $line | awk '{print $4}')
          dev_adapter=$(echo $line | awk '{print $5}')
          dev_mode=$(echo $line | awk '{print $6}')
          log "dev_mode=[$dev_mode]"
          if [ "$dev_mode" == "station" ]; then
            #iwctl station wlan0 scan
            tmpfile=$(mktemp)
            iwctl station $wifi_dev scan 2>&1 | tee $tmpfile
            log_from_file $tmpfile
            rm -f $tmpfile
            sleep 3

            #iwctl station wlan0 get-networks
            tmpfile=$(mktemp)
            iwctl station $wifi_dev get-networks 2>&1 | tee $tmpfile
            log_from_file $tmpfile
            rm -f $tmpfile

            #iwctl --passphrase "mysecret" station wlan0 connect "SSID"
            log "iwctl --passphrase $wifi_pass station $wifi_dev connect $wifi_net"
            iwctl --passphrase "$wifi_pass" station $wifi_dev connect "$wifi_net"
            sleep 3

            #iwctl station wlan0 show
            tmpfile=$(mktemp)
            iwctl station $wifi_dev show 2>&1 | tee $tmpfile
            log_from_file $tmpfile
            rm -f $tmpfile
            log "/etc/init.d/iwd restart"
            /etc/init.d/iwd restart
            sleep 1
          fi
        fi
      fi
      #iwctl station wlan0 scan
      #iwctl station wlan0 get-networks
      #iwctl --passphrase "mysecret" station wlan0 connect "SSID"
      #iwctl station wlan0 show
      #/etc/init.d/iwd restart
      #iwctl station wlan0 disconnect
    fi #endof wifi_backend==iwd
  fi
}

# check WAN interface
function check_wan_iface()
{
  local COUNTER=0
  local dev_array=($(ip route | awk '/default/ {print $5}'))
  local iface_dev=${dev_array[0]}
  while [ "$iface_dev"x == x ]; do
    sleep 1
    ((COUNTER++))
    if [ $COUNTER -gt 29 ]; then
      log "Can't get default route device interface, exit now"
      exit 1
    fi
    dev_array=($(ip route | awk '/default/ {print $5}'))
    iface_dev=${dev_array[0]}
  done

  COUNTER=0
  local WAN_IP=$(ip addr show $iface_dev | grep "inet" | head -n 1 | awk '/inet/ {print $2}' | cut -d'/' -f1)
  while [ "${WAN_IP}"x == x ]; do
    sleep 1
    ((COUNTER++))
    if [ $COUNTER -gt 29 ]; then
      log "Can't get WAN IP address, reboot now"
      force_reboot
    fi
    log "get WAN IP address again"
    WAN_IP=$(ip addr show $iface_dev | grep "inet" | head -n 1 | awk '/inet/ {print $2}' | cut -d'/' -f1)
  done
  log "WAN IP=[${WAN_IP}]"
}

function make_sysupgrade_service()
{
  # generate /etc/zzz-sysupgrade
  zzz_sysupgrade=/etc/zzz-sysupgrade
  sysupgrade_lock=/tmp/.sys-upgrade.lock

  log "make $zzz_sysupgrade"

  sysupgrade_file=$(realpath "$GLB_WORK_DIR/yandi-sys-upgrade.sh")

  if [ ! -f $sysupgrade_file ]; then
    log "No sys upgrade script $sysupgrade_file"
  fi

  if [ ! -x $sysupgrade_file ]; then
    log "first sys upgrade script $sysupgrade_file is not executive"
  fi

  if [ ! -f $zzz_sysupgrade ]; then
    touch $zzz_sysupgrade
    cat <<EOF >>$zzz_sysupgrade
#!/bin/sh -e

if [ -x $sysupgrade_file ]; then
  (flock -x $sysupgrade_lock -c "$sysupgrade_file")&
fi
EOF
    chmod a+x $zzz_sysupgrade
  else
    real_zzz_sysupgrade=$(realpath $zzz_sysupgrade)
    sed "s#/etc/kpc/sysupgrade/guest/yandi-sys-upgrade.sh#$sysupgrade_file#g" -i $real_zzz_sysupgrade
    chmod a+x $real_zzz_sysupgrade
  fi
}

function run_backup_all_data()
{
  if [ ! -d $GLB_SYSUPGRADE_BAK ]; then
    log "rm -rf $GLB_SYSUPGRADE_BAK"
    rm -rf $GLB_SYSUPGRADE_BAK

    log "mkdir -p $GLB_SYSUPGRADE_BAK"
    mkdir -p $GLB_SYSUPGRADE_BAK
  fi
  local tmpfile=$(mktemp)
  tar -zcf $tmpfile /etc
  cp -af $tmpfile "$GLB_SYSUPGRADE_BAK"/etc.tgz
  rm -f $tmpfile

  if [ -d $GLB_SYSUPGRADE_BAK/var/lib ]; then
    log "mkdir -p $GLB_SYSUPGRADE_BAK/var/lib"
    mkdir -p $GLB_SYSUPGRADE_BAK/var/lib/dpkg
  fi
  log "cp -af /var/lib/dpkg/* $GLB_SYSUPGRADE_BAK/var/lib/dpkg/"
  cp -af /var/lib/dpkg/* $GLB_SYSUPGRADE_BAK/var/lib/dpkg/

  if [ -d $GLB_SYSUPGRADE_BAK/var/lib/apt ]; then
    log "mkdir -p $GLB_SYSUPGRADE_BAK/var/lib/apt"
    mkdir -p $GLB_SYSUPGRADE_BAK/var/lib/apt
  fi
  log "cp -af /var/lib/apt/extended_states $GLB_SYSUPGRADE_BAK/var/lib/apt/extended_states"
  cp -af /var/lib/apt/extended_states $GLB_SYSUPGRADE_BAK/var/lib/apt/extended_states

  log "dpkg --get-selections '*' >$GLB_SYSUPGRADE_BAK/dpkg-get-selections.txt"
  dpkg --get-selections '*' >$GLB_SYSUPGRADE_BAK/dpkg-get-selections.txt
}

function run_minimal_system_upgrade()
{
  apt upgrade --without-new-pkgs
}

function run_full_system_upgrade()
{
  apt full-upgrade -o APT::Immediate-Configure=0
}

# 0. init script
log_init
log "[prepare-sysupgrade] START"
cleanup_firstboot_script
fix_possible_broken
get_work_dir
if [ -f $GLB_DONE_FILE ]; then
  log "exist file $GLB_DONE_FILE , exit now"
  exit 1
fi
get_root_dev
start_heartbeat
run_bootstrap_config
check_wan_iface
save_pkg_list pre_prepare_sysupgrade_pkgs.txt

log "PATH=[$PATH]"

# 1. upgrade current distro to latest point of release
export DEBIAN_FRONTEND=noninteractive
export DEBIAN_PRIORITY=critical
tmpfile=$(mktemp)
apt-get update 2>&1 | tee $tmpfile
log_from_file $tmpfile
rm -f $tmpfile
tmpfile=$(mktemp)
apt-get upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" 2>&1 | tee $tmpfile
log_from_file $tmpfile
rm -f $tmpfile

# plymouth makes initramfs-tool failed
log "apt-get purge plymouth -y"
apt-get purge plymouth -y

log "fix potential dpkg issues"
dpkg -C
dpkg --configure -a

# 2. Back up any data or configuration information
run_backup_all_data

# 3. Prepare a safe environment for the upgrade
service micro-evtd stop
if [ -x /usr/sbin/microapl ]; then
  /usr/sbin/microapl -a system_set_watchdog off
fi

# 4. banner
if [ -f "$GLB_SYSUPGRADE_ROOT/etc/banner" ]; then
  log "copy $GLB_SYSUPGRADE_ROOT/etc/banner to /etc/banner"
  cp -f "$GLB_SYSUPGRADE_ROOT"/etc/banner /etc/banner
fi

# 5. /etc
if [ -e "$GLB_SYSUPGRADE_ROOT/etc" ]; then
  log "copy $GLB_SYSUPGRADE_ROOT/etc/* to /etc/"
  cp -af "$GLB_SYSUPGRADE_ROOT"/etc/* /etc/
fi

# 6. /lib
if [ -e "$GLB_SYSUPGRADE_ROOT/lib" ]; then
  log "copy $GLB_SYSUPGRADE_ROOT/lib/* to /lib/"
  cp -af "$GLB_SYSUPGRADE_ROOT"/lib/* /lib/
fi

# 7. /usr/sbin
if [ -e "$GLB_SYSUPGRADE_ROOT/usr/sbin" ]; then
  log "copy $GLB_SYSUPGRADE_ROOT/usr/sbin/* to /usr/sbin/"
  cp -af "$GLB_SYSUPGRADE_ROOT"/usr/sbin/* /usr/sbin/
fi

# 8. generate zzz-sysupgrade based on $GLB_WORK_DIR
make_sysupgrade_service

save_pkg_list post_prepare_sysupgrade_pkgs.txt
touch $GLB_DONE_FILE
sync
#sleep 1

log "[prepare-sysupgrade] EXIT"
#sleep 1

# restart system
#poweroff

